/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as path from 'path';

import { ICallback } from './utils/wireProtocol';

//import { workspace, window, Uri, CancellationToken, Disposable, Memento, MessageItem, EventEmitter, Event, commands, env } from 'vscode';
//import * as Proto from './protocol';
import { ITypeScriptServiceClient, ITypeScriptServiceClientHost } from './typescriptService';
import { TypeScriptServerPlugin } from './utils/plugins';

import * as is from './utils/is';
import API from './utils/api';

import { TypeScriptServiceConfiguration } from './utils/configuration';
import * as fileSchemes from './utils/fileSchemes';
import { inferredProjectConfig } from './utils/tsconfig';

interface CallbackItem {
	c: (value: any) => void;
	e: (err: any) => void;
	start: number;
}

class CallbackMap {
	private readonly callbacks: Map<number, CallbackItem> = new Map();
	public pendingResponses: number = 0;

	public destroy(e: any): void {
		for (const callback of this.callbacks.values()) {
			callback.e(e);
		}
		this.callbacks.clear();
		this.pendingResponses = 0;
	}

	public add(seq: number, callback: CallbackItem) {
		this.callbacks.set(seq, callback);
		++this.pendingResponses;
	}

	public fetch(seq: number): CallbackItem | undefined {
		const callback = this.callbacks.get(seq);
		this.delete(seq);
		return callback;
	}

	private delete(seq: number) {
		if (this.callbacks.delete(seq)) {
			--this.pendingResponses;
		}
	}
}

interface RequestItem {
	request: any;
	callbacks: CallbackItem | null;
}

class RequestQueue {
	private queue: RequestItem[] = [];
	private sequenceNumber: number = 0;

	public get length(): number {
		return this.queue.length;
	}

	public push(item: RequestItem): void {
		this.queue.push(item);
	}

	public shift(): RequestItem | undefined {
		return this.queue.shift();
	}

	public tryCancelPendingRequest(seq: number): boolean {
		for (let i = 0; i < this.queue.length; i++) {
			if (this.queue[i].request.seq === seq) {
				this.queue.splice(i, 1);
				return true;
			}
		}
		return false;
	}

	public createRequest(command: string, args: any) {
		return {
			seq: this.sequenceNumber++,
			type: 'request',
			command: command,
			arguments: args
		};
	}
}

class ForkedTsServerProcess {
	private hub: any;
	constructor(
		//private childProcess: cp.ChildProcess
	) {
		this.hub = ((<any>window)["Hub"]).typescript;

	}

	public onError(cb: (err: Error) => void): void {
		this.hub.on("typescriptServerError", cb);
		//this.childProcess.on('error', cb);
	}

	public onExit(cb: (err: any) => void): void {
		this.hub.on("typescriptServerExit", cb);
		//this.childProcess.on('exit', cb);
	}

	public write(serverRequest: any) {
		this.hub.call("write", JSON.stringify(serverRequest) + '\r\n');
		//this.childProcess.stdin.write(JSON.stringify(serverRequest) + '\r\n', 'utf8');
	}

	public createReader(
		callback: ICallback<any>,
		onError: (error: any) => void = () => ({})
	) {
		// tslint:disable-next-line:no-unused-expression
		this.hub.on("typescriptServerData", (data: any) => {
			if (data.success) {
				callback(data.content);
				return;
			}
			onError(data.error);
		});
		//new Reader<any>(this.childProcess.stdout, callback, onError);
	}

	public kill() {
		this.hub.call("kill");
		//this.childProcess.kill();
	}
}

export default class TypeScriptServiceClient implements ITypeScriptServiceClient {
	private static readonly WALK_THROUGH_SNIPPET_SCHEME_COLON = `${fileSchemes.walkThroughSnippet}:`;

	private pathSeparator: string;

	private _onReady?: { promise: Promise<void>; resolve: () => void; reject: () => void; };
	private _configuration: TypeScriptServiceConfiguration;

	private tsServerLogFile: string | null = null;
	private servicePromise: any | null;
	private lastError: Error | null;
	private lastStart: number;
	private numberRestarts: number;
	private isRestarting: boolean = false;

	private cancellationPipeName: string | null = null;

	private requestQueue: RequestQueue;
	private callbacks: CallbackMap;

	private readonly _onTsServerStarted: any;
	private readonly _onProjectLanguageServiceStateChanged: any;
	private readonly _onDidBeginInstallTypings: any;
	private readonly _onDidEndInstallTypings: any;
	private readonly _onTypesInstallerInitializationFailed: any;

	/**
	 * API version obtained from the version picker after checking the corresponding path exists.
	 */
	private _apiVersion: API;
	/**
	 * Version reported by currently-running tsserver.
	 */
	//private _tsserverVersion: string | undefined;

	private readonly disposables: any[] = [];

	constructor(
		private readonly host: ITypeScriptServiceClientHost,
		public readonly plugins: TypeScriptServerPlugin[],
		private vscode: any
	) {
		this._onTsServerStarted = new vscode.EventEmitter();
		this._onProjectLanguageServiceStateChanged = new vscode.EventEmitter();
		this._onDidBeginInstallTypings = new vscode.EventEmitter();
		this._onDidEndInstallTypings = new vscode.EventEmitter();
		this._onTypesInstallerInitializationFailed = new vscode.EventEmitter();
		this.pathSeparator = path.sep;
		this.lastStart = Date.now();

		var p = new Promise<void>((resolve, reject) => {
			this._onReady = { promise: p, resolve, reject };
		});
		this._onReady!.promise = p;

		this.servicePromise = null;
		this.lastError = null;
		this.numberRestarts = 0;

		this.requestQueue = new RequestQueue();
		this.callbacks = new CallbackMap();
		this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace(vscode);

		this._apiVersion = API.defaultVersion;
		//this._tsserverVersion = undefined;

		// vscode.workspace.onDidChangeConfiguration(() => {
		// 	const oldConfiguration = this._configuration;
		// 	this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace();

		// 	this.versionProvider.updateConfiguration(this._configuration);
		// 	this.tracer.updateConfiguration();

		// 	if (this.servicePromise) {
		// 		if (this._configuration.checkJs !== oldConfiguration.checkJs
		// 			|| this._configuration.experimentalDecorators !== oldConfiguration.experimentalDecorators
		// 		) {
		// 			this.setCompilerOptionsForInferredProjects(this._configuration);
		// 		}

		// 		if (!this._configuration.isEqualTo(oldConfiguration)) {
		// 			this.restartTsServer();
		// 		}
		// 	}
		// }, this, this.disposables);
	}

	public get configuration() {
		return this._configuration;
	}

	public dispose() {
		if (this.servicePromise) {
			this.servicePromise.then((childProcess: any) => {
				childProcess.kill();
			}).then(undefined, (): any => void 0);
		}

		while (this.disposables.length) {
			const obj = this.disposables.pop();
			if (obj) {
				obj.dispose();
			}
		}
	}

	// public restartTsServer(): void {
	// 	const start = () => {
	// 		this.servicePromise = this.startService(true);
	// 		return this.servicePromise;
	// 	};

	// 	if (this.servicePromise) {
	// 		this.servicePromise = this.servicePromise.then((childProcess: any) => {
	// 			this.info('Killing TS Server');
	// 			this.isRestarting = true;
	// 			childProcess.kill();
	// 			this.resetClientVersion();
	// 		}).then(start);
	// 	} else {
	// 		start();
	// 	}
	// }

	get onTsServerStarted() {
		return this._onTsServerStarted.event;
	}

	get onProjectLanguageServiceStateChanged() {
		return this._onProjectLanguageServiceStateChanged.event;
	}

	get onDidBeginInstallTypings() {
		return this._onDidBeginInstallTypings.event;
	}

	get onDidEndInstallTypings() {
		return this._onDidEndInstallTypings.event;
	}

	get onTypesInstallerInitializationFailed() {
		return this._onTypesInstallerInitializationFailed.event;
	}

	public get apiVersion(): API {
		return this._apiVersion;
	}

	public onReady(f: () => void): Promise<void> {
		return this._onReady!.promise.then(f);
	}

	private info(message: string, data?: any): void {
		console.info(message, data);
	}

	public warn(message: string, data?: any): void {
		console.warn(message, data);
	}

	private error(message: string, data?: any): void {
		console.error(message, data);
	}

	public logTelemetry(eventName: string, properties?: { [prop: string]: string }) {
		console.info(eventName, properties);
	}

	private service(): Promise<ForkedTsServerProcess> {
		if (this.servicePromise) {
			return this.servicePromise;
		}
		if (this.lastError) {
			return Promise.reject<ForkedTsServerProcess>(this.lastError);
		}
		this.startService();
		if (this.servicePromise) {
			return this.servicePromise;
		}
		return Promise.reject<ForkedTsServerProcess>(new Error('Could not create TS service'));
	}

	public ensureServiceStarted() {
		if (!this.servicePromise) {
			this.startService();
		}
	}

	private startService(resendModels: boolean = false): Promise<ForkedTsServerProcess> {
		// let currentVersion = this.versionPicker.currentVersion;

		// this.info(`Using tsserver from: ${currentVersion.path}`);
		// if (!fs.existsSync(currentVersion.tsServerPath)) {
		// 	window.showWarningMessage(localize('noServerFound', 'The path {0} doesn\'t point to a valid tsserver install. Falling back to bundled TypeScript version.', currentVersion.path));

		// 	this.versionPicker.useBundledVersion();
		// 	currentVersion = this.versionPicker.currentVersion;
		// }

		// this._apiVersion = this.versionPicker.currentVersion.version || API.defaultVersion;
		// this.onDidChangeTypeScriptVersion(currentVersion);

		this.requestQueue = new RequestQueue();
		this.callbacks = new CallbackMap();
		this.lastError = null;

		var hub = ((<any>window)["Hub"]).typescript;
		return this.servicePromise = hub.call("startServer").then(() => {
			const handle = new ForkedTsServerProcess();
			this.lastStart = Date.now();

			handle.onError((err: Error) => {
				this.lastError = err;
				this.error('TSServer errored with error.', err);
				if (this.tsServerLogFile) {
					this.error(`TSServer log file: ${this.tsServerLogFile}`);
				}
				/* __GDPR__
					"tsserver.error" : {}
				*/
				this.logTelemetry('tsserver.error');
				this.serviceExited(false);
			});
			handle.onExit((code: any) => {
				if (code === null || typeof code === 'undefined') {
					this.info('TSServer exited');
				} else {
					this.error(`TSServer exited with code: ${code}`);
					/* __GDPR__
						"tsserver.exitWithCode" : {
							"code" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
						}
					*/
					this.logTelemetry('tsserver.exitWithCode', { code: code });
				}

				if (this.tsServerLogFile) {
					this.info(`TSServer log file: ${this.tsServerLogFile}`);
				}
				this.serviceExited(!this.isRestarting);
				this.isRestarting = false;
			});

			handle.createReader(
				msg => { this.dispatchMessage(msg); },
				error => { this.error('ReaderError', error); });

			this._onReady!.resolve();
			//resolve(handle);
			this._onTsServerStarted.fire();
			this.serviceStarted(resendModels);
			return handle;
		}, (msg: any) => {
			console.error(msg);
			alert(msg);
		});
		// new Promise<ForkedTsServerProcess>(async (resolve, reject) => {
		// 	try {
		// 		const tsServerForkArgs = await this.getTsServerArgs(currentVersion);
		// 		const debugPort = this.getDebugPort();
		// 		const tsServerForkOptions: electron.IForkOptions = {
		// 			execArgv: debugPort ? [`--inspect=${debugPort}`] : [] // [`--debug-brk=5859`]
		// 		};
		// 		electron.fork(currentVersion.tsServerPath, tsServerForkArgs, tsServerForkOptions, this.logger, (err: any, childProcess: cp.ChildProcess | null) => {
		// 			if (err || !childProcess) {
		// 				this.lastError = err;
		// 				this.error('Starting TSServer failed with error.', err);
		// 				window.showErrorMessage(localize('serverCouldNotBeStarted', 'TypeScript language server couldn\'t be started. Error message is: {0}', err.message || err));
		// 				/* __GDPR__
		// 					"error" : {}
		// 				*/
		// 				this.logTelemetry('error');
		// 				this.resetClientVersion();
		// 				return;
		// 			}

		// 			this.info('Started TSServer');
		// 			const handle = new ForkedTsServerProcess(childProcess);
		// 			this.lastStart = Date.now();

		// 			handle.onError((err: Error) => {
		// 				this.lastError = err;
		// 				this.error('TSServer errored with error.', err);
		// 				if (this.tsServerLogFile) {
		// 					this.error(`TSServer log file: ${this.tsServerLogFile}`);
		// 				}
		// 				/* __GDPR__
		// 					"tsserver.error" : {}
		// 				*/
		// 				this.logTelemetry('tsserver.error');
		// 				this.serviceExited(false);
		// 			});
		// 			handle.onExit((code: any) => {
		// 				if (code === null || typeof code === 'undefined') {
		// 					this.info('TSServer exited');
		// 				} else {
		// 					this.error(`TSServer exited with code: ${code}`);
		// 					/* __GDPR__
		// 						"tsserver.exitWithCode" : {
		// 							"code" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
		// 						}
		// 					*/
		// 					this.logTelemetry('tsserver.exitWithCode', { code: code });
		// 				}

		// 				if (this.tsServerLogFile) {
		// 					this.info(`TSServer log file: ${this.tsServerLogFile}`);
		// 				}
		// 				this.serviceExited(!this.isRestarting);
		// 				this.isRestarting = false;
		// 			});

		// 			handle.createReader(
		// 				msg => { this.dispatchMessage(msg); },
		// 				error => { this.error('ReaderError', error); });

		// 			this._onReady!.resolve();
		// 			resolve(handle);
		// 			this._onTsServerStarted.fire();

		// 			this.serviceStarted(resendModels);
		// 		});
		// 	} catch (error) {
		// 		reject(error);
		// 	}
		// });
	}

	// public onVersionStatusClicked(): Promise<void> {
	// 	return this.showVersionPicker(false);
	// }

	// private showVersionPicker(firstRun: boolean): Promise<void> {
	// 	return this.versionPicker.show(firstRun).then(change => {
	// 		if (firstRun || !change.newVersion || !change.oldVersion || change.oldVersion.path === change.newVersion.path) {
	// 			return;
	// 		}
	// 		this.restartTsServer();
	// 	});
	// }

	private serviceStarted(resendModels: boolean): void {
		const configureOptions = {
			hostInfo: 'vscode'
		};
		this.execute('configure', configureOptions);
		this.setCompilerOptionsForInferredProjects(this._configuration);
		if (resendModels) {
			this.host.populateService();
		}
	}

	private setCompilerOptionsForInferredProjects(configuration: TypeScriptServiceConfiguration): void {
		if (!this.apiVersion.has206Features()) {
			return;
		}

		const args = {
			options: this.getCompilerOptionsForInferredProjects(configuration)
		};
		this.execute('compilerOptionsForInferredProjects', args, true);
	}

	private getCompilerOptionsForInferredProjects(configuration: TypeScriptServiceConfiguration) {
		return {
			...inferredProjectConfig(configuration),
			allowJs: true,
			allowSyntheticDefaultImports: true,
			allowNonTsExtensions: true,
		};
	}

	private serviceExited(restart: boolean): void {
		// enum MessageAction {
		// 	reportIssue
		// }

		this.servicePromise = null;
		this.tsServerLogFile = null;
		this.callbacks.destroy(new Error('Service died.'));
		this.callbacks = new CallbackMap();
		if (!restart) {
			//this.resetClientVersion();
		}
		else {
			const diff = Date.now() - this.lastStart;
			this.numberRestarts++;
			let startService = true;
			if (this.numberRestarts > 5) {
				//let prompt: Promise<any | undefined> | undefined = undefined;
				this.numberRestarts = 0;
				if (diff < 10 * 1000 /* 10 seconds */) {
					this.lastStart = Date.now();
					startService = false;
					// prompt = window.showErrorMessage<MyMessageItem>(
					// 	localize('serverDiedAfterStart', 'The TypeScript language service died 5 times right after it got started. The service will not be restarted.'),
					// 	{
					// 		title: localize('serverDiedReportIssue', 'Report Issue'),
					// 		id: MessageAction.reportIssue,
					// 		isCloseAffordance: true
					// 	});
					/* __GDPR__
						"serviceExited" : {}
					*/
					this.logTelemetry('serviceExited');
					//this.resetClientVersion();
				} else if (diff < 60 * 1000 /* 1 Minutes */) {
					this.lastStart = Date.now();
					// prompt = window.showWarningMessage<MyMessageItem>(
					// 	localize('serverDied', 'The TypeScript language service died unexpectedly 5 times in the last 5 Minutes.'),
					// 	{
					// 		title: localize('serverDiedReportIssue', 'Report Issue'),
					// 		id: MessageAction.reportIssue,
					// 		isCloseAffordance: true
					// 	});
				}
				// if (prompt) {
				// 	prompt.then(item => {
				// 		if (item && item.id === MessageAction.reportIssue) {
				// 			return commands.executeCommand('workbench.action.reportIssues');
				// 		}
				// 		return undefined;
				// 	});
				// }
			}
			if (startService) {
				this.startService(true);
			}
		}
	}

	public normalizePath(resource: any): string | null {
		if (this._apiVersion.has213Features()) {
			if (resource.scheme === fileSchemes.walkThroughSnippet || resource.scheme === fileSchemes.untitled) {
				const dirName = path.dirname(resource.path);
				const fileName = this.inMemoryResourcePrefix + path.basename(resource.path);
				return resource.with({ path: path.join(dirName, fileName) }).toString(true);
			}
		}

		if (resource.scheme !== fileSchemes.file) {
			return null;
		}

		const result = resource.fsPath;
		if (!result) {
			return null;
		}

		// Both \ and / must be escaped in regular expressions
		return result.replace(new RegExp('\\' + this.pathSeparator, 'g'), '/');
	}

	private get inMemoryResourcePrefix(): string {
		return this._apiVersion.has270Features() ? '^' : '';
	}

	public asUrl(filepath: string) {
		if (this._apiVersion.has213Features()) {
			if (filepath.startsWith(TypeScriptServiceClient.WALK_THROUGH_SNIPPET_SCHEME_COLON) || (filepath.startsWith(fileSchemes.untitled + ':'))
			) {
				let resource = this.vscode.Uri.parse(filepath);
				if (this.inMemoryResourcePrefix) {
					const dirName = path.dirname(resource.path);
					const fileName = path.basename(resource.path);
					if (fileName.startsWith(this.inMemoryResourcePrefix)) {
						resource = resource.with({ path: path.join(dirName, fileName.slice(this.inMemoryResourcePrefix.length)) });
					}
				}
				return resource;
			}
		}
		return this.vscode.Uri.file(filepath);
	}

	public getWorkspaceRootForResource(resource: any): string | undefined {
		const roots = this.vscode.workspace.workspaceFolders;
		if (!roots || !roots.length) {
			return undefined;
		}

		if (resource.scheme === fileSchemes.file || resource.scheme === fileSchemes.untitled) {
			for (const root of roots.sort((a: any, b: any) => a.uri.fsPath.length - b.uri.fsPath.length)) {
				if (resource.fsPath.startsWith(root.uri.fsPath + path.sep)) {
					return root.uri.fsPath;
				}
			}
			return roots[0].uri.fsPath;
		}

		return undefined;
	}

	public execute(command: string, args: any, expectsResultOrToken?: boolean | any): Promise<any> {
		let token: any | undefined = undefined;
		let expectsResult = true;
		if (typeof expectsResultOrToken === 'boolean') {
			expectsResult = expectsResultOrToken;
		} else {
			token = expectsResultOrToken;
		}

		const request = this.requestQueue.createRequest(command, args);
		const requestInfo: RequestItem = {
			request: request,
			callbacks: null
		};
		let result: Promise<any>;
		if (expectsResult) {
			let wasCancelled = false;
			result = new Promise<any>((resolve, reject) => {
				requestInfo.callbacks = { c: resolve, e: reject, start: Date.now() };
				if (token) {
					token.onCancellationRequested(() => {
						wasCancelled = true;
						this.tryCancelRequest(request.seq);
					});
				}
			}).catch((err: any) => {
				if (!wasCancelled) {
					this.error(`'${command}' request failed with error.`, err);
					const properties = this.parseErrorText(err && err.message, command);
					this.logTelemetry('languageServiceErrorResponse', properties);
				}
				throw err;
			});
		} else {
			result = Promise.resolve(null);
		}
		this.requestQueue.push(requestInfo);
		this.sendNextRequests();

		return result;
	}

	/**
	 * Given a `errorText` from a tsserver request indicating failure in handling a request,
	 * prepares a payload for telemetry-logging.
	 */
	private parseErrorText(errorText: string | undefined, command: string) {
		const properties: ObjectMap<string> = Object.create(null);
		properties['command'] = command;
		if (errorText) {
			properties['errorText'] = errorText;

			const errorPrefix = 'Error processing request. ';
			if (errorText.startsWith(errorPrefix)) {
				const prefixFreeErrorText = errorText.substr(errorPrefix.length);
				const newlineIndex = prefixFreeErrorText.indexOf('\n');
				if (newlineIndex >= 0) {
					// Newline expected between message and stack.
					properties['message'] = prefixFreeErrorText.substring(0, newlineIndex);
					properties['stack'] = prefixFreeErrorText.substring(newlineIndex + 1);
				}
			}
		}
		return properties;
	}

	private sendNextRequests(): void {
		while (this.callbacks.pendingResponses === 0 && this.requestQueue.length > 0) {
			const item = this.requestQueue.shift();
			if (item) {
				this.sendRequest(item);
			}
		}
	}

	private sendRequest(requestItem: RequestItem): void {
		const serverRequest = requestItem.request;
		if (requestItem.callbacks) {
			this.callbacks.add(serverRequest.seq, requestItem.callbacks);
		}
		this.service()
			.then((childProcess) => {
				childProcess.write(serverRequest);
			})
			.then(undefined, err => {
				const callback = this.callbacks.fetch(serverRequest.seq);
				if (callback) {
					callback.e(err);
				}
			});
	}

	private tryCancelRequest(seq: number): boolean {
		try {
			if (this.requestQueue.tryCancelPendingRequest(seq)) {
				//login.logTrace(`TypeScript Service: canceled request with sequence number ${seq}`);
				return true;
			}

			if (this.apiVersion.has222Features() && this.cancellationPipeName) {
				//this.tracer.logTrace(`TypeScript Service: trying to cancel ongoing request with sequence number ${seq}`);
				try {
					//fs.writeFileSync(this.cancellationPipeName + seq, '');
				} catch {
					// noop
				}
				return true;
			}

			//this.tracer.logTrace(`TypeScript Service: tried to cancel request with sequence number ${seq}. But request got already delivered.`);
			return false;
		} finally {
			const p = this.callbacks.fetch(seq);
			if (p) {
				p.e(new Error(`Cancelled Request ${seq}`));
			}
		}
	}

	private dispatchMessage(message: any): void {
		try {
			if (message.type === 'response') {
				const response = message;
				const p = this.callbacks.fetch(response.request_seq);
				if (p) {
					//this.tracer.traceResponse(response, p.start);
					if (response.success) {
						p.c(response);
					} else {
						p.e(response);
					}
				}
			} else if (message.type === 'event') {
				const event = message;
				//this.tracer.traceEvent(event);
				this.dispatchEvent(event);
			} else {
				throw new Error('Unknown message type ' + message.type + ' recevied');
			}
		} finally {
			this.sendNextRequests();
		}
	}

	private dispatchEvent(event: any) {
		switch (event.event) {
			case 'syntaxDiag':
				this.host.syntaxDiagnosticsReceived(event);
				break;

			case 'semanticDiag':
				this.host.semanticDiagnosticsReceived(event);
				break;

			case 'configFileDiag':
				this.host.configFileDiagnosticsReceived(event);
				break;

			case 'telemetry':
				const telemetryData = event.body;
				this.dispatchTelemetryEvent(telemetryData);
				break;

			case 'projectLanguageServiceState':
				if (event.body) {
					this._onProjectLanguageServiceStateChanged.fire(event.body);
				}
				break;

			case 'beginInstallTypes':
				if (event.body) {
					this._onDidBeginInstallTypings.fire(event.body);
				}
				break;

			case 'endInstallTypes':
				if (event.body) {
					this._onDidEndInstallTypings.fire(event.body);
				}
				break;

			case 'typesInstallerInitializationFailed':
				if (event.body) {
					this._onTypesInstallerInitializationFailed.fire(event.body);
				}
				break;
		}
	}

	private dispatchTelemetryEvent(telemetryData: any): void {
		const properties: ObjectMap<string> = Object.create(null);
		switch (telemetryData.telemetryEventName) {
			case 'typingsInstalled':
				const typingsInstalledPayload = telemetryData.payload;
				properties['installedPackages'] = typingsInstalledPayload.installedPackages;

				if (is.defined(typingsInstalledPayload.installSuccess)) {
					properties['installSuccess'] = typingsInstalledPayload.installSuccess.toString();
				}
				if (is.string(typingsInstalledPayload.typingsInstallerVersion)) {
					properties['typingsInstallerVersion'] = typingsInstalledPayload.typingsInstallerVersion;
				}
				break;

			default:
				const payload = telemetryData.payload;
				if (payload) {
					Object.keys(payload).forEach((key) => {
						try {
							if (payload.hasOwnProperty(key)) {
								properties[key] = is.string(payload[key]) ? payload[key] : JSON.stringify(payload[key]);
							}
						} catch (e) {
							// noop
						}
					});
				}
				break;
		}
		// if (telemetryData.telemetryEventName === 'projectInfo') {
		// 	this._tsserverVersion = properties['version'];
		// }

		/* __GDPR__
			"typingsInstalled" : {
				"installedPackages" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
				"installSuccess": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
				"typingsInstallerVersion": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
			}
		*/
		// __GDPR__COMMENT__: Other events are defined by TypeScript.
		this.logTelemetry(telemetryData.telemetryEventName, properties);
	}

	// private async getTsServerArgs(
	// 	currentVersion: TypeScriptVersion
	// ): Promise<string[]> {
	// 	const args: string[] = [];

	// 	if (this.apiVersion.has206Features()) {
	// 		if (this.apiVersion.has250Features()) {
	// 			args.push('--useInferredProjectPerProjectRoot');
	// 		} else {
	// 			args.push('--useSingleInferredProject');
	// 		}

	// 		if (this._configuration.disableAutomaticTypeAcquisition) {
	// 			args.push('--disableAutomaticTypingAcquisition');
	// 		}
	// 	}

	// 	if (this.apiVersion.has208Features()) {
	// 		args.push('--enableTelemetry');
	// 	}

	// 	if (this.apiVersion.has222Features()) {
	// 		this.cancellationPipeName = electron.getTempFile(`tscancellation-${electron.makeRandomHexString(20)}`);
	// 		args.push('--cancellationPipeName', this.cancellationPipeName + '*');
	// 	}

	// 	if (this.apiVersion.has222Features()) {
	// 		if (this._configuration.tsServerLogLevel !== TsServerLogLevel.Off) {
	// 			const logDir = await this.logDirectoryProvider.getNewLogDirectory();
	// 			if (logDir) {
	// 				this.tsServerLogFile = path.join(logDir, `tsserver.log`);
	// 				this.info(`TSServer log file: ${this.tsServerLogFile}`);
	// 			} else {
	// 				this.tsServerLogFile = null;
	// 				this.error('Could not create TSServer log directory');
	// 			}

	// 			if (this.tsServerLogFile) {
	// 				args.push('--logVerbosity', TsServerLogLevel.toString(this._configuration.tsServerLogLevel));
	// 				args.push('--logFile', this.tsServerLogFile);
	// 			}
	// 		}
	// 	}

	// 	if (this.apiVersion.has230Features()) {
	// 		if (this.plugins.length) {
	// 			args.push('--globalPlugins', this.plugins.map(x => x.name).join(','));
	// 			if (currentVersion.path === this.versionProvider.defaultVersion.path) {
	// 				args.push('--pluginProbeLocations', this.plugins.map(x => x.path).join(','));
	// 			}
	// 		}
	// 	}

	// 	if (this.apiVersion.has234Features()) {
	// 		if (this._configuration.npmLocation) {
	// 			args.push('--npmLocation', `"${this._configuration.npmLocation}"`);
	// 		}
	// 	}

	// 	if (this.apiVersion.has260Features()) {
	// 		const tsLocale = getTsLocale(this._configuration);
	// 		if (tsLocale) {
	// 			args.push('--locale', tsLocale);
	// 		}
	// 	}
	// 	return args;
	// }


	// private getDebugPort(): number | undefined {
	// 	const value = process.env['TSS_DEBUG'];
	// 	if (value) {
	// 		const port = parseInt(value);
	// 		if (!isNaN(port)) {
	// 			return port;
	// 		}
	// 	}
	// 	return undefined;
	// }

	// private resetClientVersion() {
	// 	this._apiVersion = API.defaultVersion;
	// 	this._tsserverVersion = undefined;
	// }
}


// const getTsLocale = (configuration: TypeScriptServiceConfiguration): string | undefined =>
// 	(configuration.locale
// 		? configuration.locale
// 		: env.language);